#include "JSON.h"
#include "JSONParser.h"

#include <sstream>
#include <stdexcept>

NahidaProject::JSON::JSON() : typeType(jsonNull){

}

NahidaProject::JSON::JSON(Type type) : typeType(type){
    switch (typeType){
        case jsonNull:
            break;
        case jsonBool:
           valueType.booleanType = false;
            break;
        case jsonInt:
            valueType.integerType = 0;
            break;
        case jsonDouble:
            valueType.doubleType = 0.0;
            break;
        case jsonString:
            valueType.stringType = new std::string("");
            break;
        case jsonArray:
            valueType.arrayType = new std::list<JSON>();
            break;
        case jsonObject:
            valueType.objectType = new std::map<std::string, JSON>();
        default:
            break;
    }
}

NahidaProject::JSON::JSON(bool value) : typeType(jsonBool){
    this->valueType.booleanType = value;
}

NahidaProject::JSON::JSON(int value) : typeType(jsonInt){
    this->valueType.integerType = value;
}

NahidaProject::JSON::JSON(double value) : typeType(jsonDouble){
	this->valueType.doubleType = value;
}

NahidaProject::JSON::JSON(const char * value) : typeType(jsonString){
	this->valueType.stringType = new std::string(value);
}

NahidaProject::JSON::JSON(const std::string & value) : typeType(jsonString){
	this->valueType.stringType = new std::string(value);
}

NahidaProject::JSON::JSON(const JSON & other) : typeType(jsonNull){
    this->Copy(other);
}

NahidaProject::JSON::~JSON(){

}

NahidaProject::JSON::Type NahidaProject::JSON::type() const{
    return this->typeType;
}

bool NahidaProject::JSON::IsNull() const{
    return this->typeType == jsonNull;
}

bool NahidaProject::JSON::IsBool() const{
    return this->typeType == jsonBool;
}

bool NahidaProject::JSON::IsInt() const{
    return this->typeType == jsonInt;
}

bool NahidaProject::JSON::IsDouble() const{
    return this->typeType == jsonDouble;
}

bool NahidaProject::JSON::IsString() const{
    return this->typeType == jsonString;
}

bool NahidaProject::JSON::IsArray() const{
    return this->typeType == jsonArray;
}

bool NahidaProject::JSON::IsObject() const{
    return this->typeType == jsonObject;
}

bool NahidaProject::JSON::AsBool() const{
    if (this->typeType == jsonBool){
        return valueType.booleanType;
    }
    throw std::logic_error("function Json::asBool value type error");
}

int NahidaProject::JSON::AsInt() const{
    if (this->typeType == jsonInt){
        return valueType.integerType;
    }
    throw std::logic_error("function Json::asInt value type error");
}

double NahidaProject::JSON::AsDouble() const{
    if (this->typeType == jsonDouble){
        return valueType.doubleType;
    }
    throw std::logic_error("function Json::asDouble value type error");
}

std::string NahidaProject::JSON::AsString() const{
    if (this->typeType == jsonString){
        return *(valueType.stringType);
    }
    throw std::logic_error("function Json::asString value type error");
}

void NahidaProject::JSON::Copy(const JSON & other){
    this->typeType = other.typeType;
    this->valueType = other.valueType;
}

int NahidaProject::JSON::Size() const{
    switch (this->typeType){
        case jsonArray:
            return (valueType.arrayType)->size();
        case jsonObject:
            return (valueType.objectType)->size();
        default:
            break;
    }
    throw std::logic_error("function Json::size value type error");
}

bool NahidaProject::JSON::Empty() const{
    switch (this->typeType){
        case jsonNull:
            return true;
        case jsonArray:
            return (valueType.arrayType)->empty();
        case jsonObject:
            return (valueType.objectType)->empty();
        default:
            break;
    }
    return false;
}

void NahidaProject::JSON::Clear(){
    switch (typeType){
        case jsonNull:
        case jsonBool:
        case jsonInt:
        case jsonDouble:
            break;
        case jsonString:
            {
                delete valueType.stringType;
            }
            break;
        case jsonArray:
            {
                for (auto it = (valueType.arrayType)->begin(); it != (valueType.arrayType)->end(); it++){
                    it->Clear();
                }
                delete valueType.arrayType;
            }
            break;
        case jsonObject:
            {
                for (auto it = (valueType.objectType)->begin(); it != (valueType.objectType)->end(); it++)
                {
                    it->second.Clear();
                }
                delete valueType.objectType;
            }
            break;
        default:
            break;
    }
    typeType = jsonNull;
}

bool NahidaProject::JSON::Has(int index)
{
    if (typeType != jsonArray)
    {
        return false;
    }
    int size = (valueType.arrayType)->size();
    return (index >= 0) && (index < size);
}

bool NahidaProject::JSON::Has(const char * key){
    std::string name(key);
    return Has(name);
}

bool NahidaProject::JSON::Has(const std::string & key)
{
    if (typeType != jsonObject)
    {
        return false;
    }
    return (valueType.objectType)->find(key) != (valueType.objectType)->end();
}

void NahidaProject::JSON::Remove(int index){
    if (typeType != jsonArray){
        return;
    }
    int size = (valueType.arrayType)->size();
    if (index < 0 || index >= size){
        return;
    }
    auto it = (valueType.arrayType)->begin();
    for (int i = 0; i < index; i++){
        it++;
    }
    it->Clear();
    (valueType.arrayType)->erase(it);
}

void NahidaProject::JSON::Remove(const char * key)
{
    std::string name = key;
    Remove(name);
}

void NahidaProject::JSON::Remove(const std::string & key)
{
    if (typeType != jsonObject)
    {
        return;
    }
    auto it = (valueType.objectType)->find(key);
    if (it != (valueType.objectType)->end())
    {
        it->second.Clear();
        (valueType.objectType)->erase(key);
    }
}

void NahidaProject::JSON::Append(const JSON & value)
{
    if (typeType != jsonArray)
    {
        Clear();
		typeType = jsonArray;
        valueType.arrayType = new std::list<JSON>();
    }
    (valueType.arrayType)->push_back(value);
}

NahidaProject::JSON & NahidaProject::JSON::operator = (const JSON & other){
    Clear();
    Copy(other);
    return *this;
}

bool NahidaProject::JSON::operator == (const JSON & other){
    if (typeType != other.type())
    {
        return false;
    }
    switch (typeType)
    {
        case jsonNull:
            return true;
        case jsonBool:
            return (valueType.booleanType == other.valueType.booleanType);
        case jsonInt:
            return (valueType.integerType == other.valueType.integerType);
        case jsonDouble:
            return (valueType.doubleType == other.valueType.doubleType);
        case jsonString:
            return *(valueType.stringType) == *(other.valueType.stringType);
        case jsonArray:
            return valueType.arrayType == other.valueType.arrayType;
        case jsonObject:
            return valueType.objectType == other.valueType.objectType;
        default:
            break;
    }
    return false;
}

bool NahidaProject::JSON::operator != (const JSON & other){
    return !(*this == other);
}

NahidaProject::JSON & NahidaProject::JSON::operator [] (int index){
    if (index < 0)
    {
        throw std::logic_error("function Json::operator [int] index less than 0");
    }
    if (typeType != jsonArray)
    {
        Clear();
        typeType = jsonArray;
        valueType.arrayType = new std::list<JSON>();
    }
    int size = (valueType.arrayType)->size();
    if (index >= 0 && index < size)
    {
        auto it = (valueType.arrayType)->begin();
        for (int i = 0; i < index; i++)
        {
            it++;
        }
        return *it;
    }
    if (index >= size)
    {
        for (int i = size; i <= index; i++)
        {
            (valueType.arrayType)->push_back(JSON());
        }
    }
    return (valueType.arrayType)->back();
}

NahidaProject::JSON & NahidaProject::JSON::operator [] (const char * key)
{
    std::string name = key;
    return (*this)[name];
}

NahidaProject::JSON & NahidaProject::JSON::operator [] (const std::string & key)
{
    if (typeType != jsonObject)
    {
        Clear();
        typeType = jsonObject;
        valueType.objectType = new std::map<std::string, JSON>();
    }
    return (*(valueType.objectType))[key];
}

NahidaProject::JSON::operator bool(){
    if (typeType != jsonBool){
        throw std::logic_error("function Json::operator (bool) requires bool value");
    }
    return valueType.booleanType;
}

NahidaProject::JSON::operator int(){
    if (typeType != jsonInt){
        throw std::logic_error("function Json::operator (int) requires int value");
    }
    return valueType.integerType;
}

NahidaProject::JSON::operator double()
{
    if (typeType != jsonDouble)
    {
        throw std::logic_error("function Json::operator (double) requires double value");
    }
    return valueType.doubleType;
}

NahidaProject::JSON::operator std::string(){
    if (typeType != jsonString)
    {
        throw std::logic_error("function Json::operator (string) requires string value");
    }
    return *(valueType.stringType);
}

void NahidaProject::JSON::Parse(const std::string & str){
    NahidaProject::JSONParser parser;
    parser.LoadFile(str);
    *this = parser.Parse();
}

std::string NahidaProject::JSON::ToString() const{
    std::stringstream ss;
    switch (typeType)
    {
        case jsonNull:
            ss << "null";
            break;
        case jsonBool:
            if (valueType.booleanType)
            {
                ss << "true";
            }
            else
            {
                ss << "false";
            }
            break;
        case jsonInt:
            ss << valueType.integerType;
            break;
        case jsonDouble:
            ss << valueType.doubleType;
            break;
        case jsonString:
            ss << "\"" << *(valueType.stringType) << "\"";
            break;
        case jsonArray:
            {
                ss << "[";
                for (auto it = (valueType.arrayType)->begin(); it != (valueType.arrayType)->end(); it++)
                {
                    if (it != (valueType.arrayType)->begin())
                    {
                        ss << ",";
                    }
					ss << (*it).ToString();
                }
                ss << "]";
            }
            break;
        case jsonObject:
            {
                ss << "{";
                for (auto it = (valueType.objectType)->begin(); it != (valueType.objectType)->end(); it++)
                {
                    if (it != (valueType.objectType)->begin())
                    {
                        ss << ",";
                    }
                    ss << "\"" << it->first << "\":" << it->second.ToString();
                }
                ss << "}";
            }
            break;
        default:
            break;
    }
    return ss.str();
}